'use client';

import {
  Combobox,
  ComboboxButton,
  ComboboxInput,
  ComboboxOption,
  ComboboxOptions,
} from '@headlessui/react';
import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import clsx from 'clsx';

export interface Option {
  label: string;
  value: string;
  icon?: React.ReactNode;
  disabled?: boolean;
}

export type Value = string | number | (string | number)[];

export interface SelectProps
  extends Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue' | 'size'> {
  className?: string;
  popoverClassName?: string;
  options?: Option[];
  multiple?: boolean;
  defaultValue?: Value;
  value?: Value;
  size?: 'sm' | 'md' | 'lg';
  placeholder?: string;
  showArrow?: boolean;
  allowSearch?: boolean;
  disabled?: boolean;
  onChange?: (values: Value) => void;
  /**
   * 搜索关键字变化时的回调，如果调用此方法，则不再使用内部搜索
   * @param query 搜索关键字
   */
  onSearchChange?: (query: string) => void | Promise<void>;
}

function SelectNoMemoized(props: SelectProps) {
  const {
    ref,
    className,
    popoverClassName,
    options,
    multiple,
    value,
    defaultValue,
    size,
    showArrow = true,
    allowSearch = false,
    placeholder,
    disabled,
    onChange,
    onSearchChange,
    ...restInputProps
  } = props;
  const _defaultValue = defaultValue ?? (multiple ? [] : '');

  const inputRef = useRef<HTMLInputElement>(null);

  const [querying, setQuerying] = useState(false);

  const [query, setQuery] = useState('');

  const [innerValue, setInnerValue] = useState<Value>(_defaultValue);

  const finalValue = useMemo(() => value ?? innerValue, [value, innerValue]);

  const finalOptions = useMemo(() => {
    if (!query) return options;
    const lowerQuery = query.toLowerCase();
    return options?.filter((option) => option.label.toLowerCase().includes(lowerQuery)) ?? [];
  }, [query, options]);

  const comboboxValue = useMemo(() => {
    if (multiple) {
      if (!Array.isArray(finalValue)) return [];
      return options?.filter((option) => finalValue.includes(option.value)) ?? [];
    }
    return options?.find((option) => option.value === finalValue) ?? null;
  }, [finalValue, multiple, options]);

  useImperativeHandle(ref, () => {
    // 响应 react-hook-form 的 ref
    // 转发 ref：使用虚拟 input 接受原始 value 数据，转换成 label 并转发给真正的 input
    const _ref = document.createElement('input');
    Object.defineProperties(_ref, {
      value: {
        set(_value) {
          if (multiple && inputRef.current) {
            // multiple select: input value is always empty
            inputRef.current.value = '';
            setInnerValue([]);
          } else if (inputRef.current) {
            // single select: set input value to the label of the selected option
            const label = options?.find((option) => option.value === _value)?.label ?? '';
            inputRef.current.value = label;
            setInnerValue(label);
          }
          return _value;
        },
      },
      focus: {
        get() {
          return () => inputRef.current?.focus();
        },
      },
      select: {
        get() {
          return () => inputRef.current?.select();
        },
      },
      setCustomValidity: {
        get() {
          return (message: string) => inputRef.current?.setCustomValidity(message);
        },
      },
      reportValidity: {
        get() {
          return () => inputRef.current?.reportValidity();
        },
      },
    });
    return _ref;
  }, [multiple, options]);

  // Handler
  const handleSearchChange = useCallback(
    (v: string) => {
      if (!allowSearch) return;
      if (onSearchChange) {
        // 外部处理搜索
        setQuerying(true);
        const cb = onSearchChange(v);
        if (cb instanceof Promise) {
          cb.finally(() => {
            setQuerying(false);
          });
        }
      } else {
        // 内部处理搜索
        setQuery(v);
      }
    },
    [allowSearch, onSearchChange]
  );

  return (
    <Combobox
      immediate={allowSearch} // 获得焦点时立即打开下拉框
      multiple={multiple}
      value={comboboxValue}
      disabled={disabled}
      onChange={(option) => {
        let nextValue: Value;
        if (multiple) {
          nextValue = Array.isArray(option) ? (option as Option[]).map((item) => item.value) : [];
        } else {
          nextValue = (option as unknown as Option)?.value ?? '';
        }
        setInnerValue(nextValue);
        onChange?.(nextValue);
      }}
      onClose={() => {
        // 清除搜索
        handleSearchChange('');
      }}
    >
      {({ open }) => {
        let showMultipleTags = false;
        if (multiple && !allowSearch) {
          showMultipleTags = true;
        } else if (multiple && allowSearch && !open) {
          showMultipleTags = true;
        }
        return (
          <div className={clsx('w-[clamp(3rem,20rem,100%)]', className)}>
            <ComboboxButton
              className="w-full relative"
              title={
                Array.isArray(comboboxValue)
                  ? comboboxValue.map((op) => op.label).join(', ')
                  : comboboxValue?.label
              }
            >
              <ComboboxInput<Option>
                {...restInputProps}
                ref={inputRef}
                as="input"
                autoComplete="off"
                className={clsx(
                  'w-full',
                  showArrow ? 'select' : 'input',
                  showArrow && size === 'sm' && 'select-sm',
                  showArrow && size === 'md' && 'select-md',
                  showArrow && size === 'lg' && 'select-lg',
                  !showArrow && size === 'sm' && 'input-sm',
                  !showArrow && size === 'md' && 'input-md',
                  !showArrow && size === 'lg' && 'input-lg',
                  !allowSearch && 'cursor-default'
                )}
                placeholder={placeholder}
                disabled={disabled}
                readOnly={!allowSearch}
                displayValue={(option) => {
                  if (!multiple) {
                    return option?.label ?? '';
                  }
                  return '';
                }}
                onChange={(e) => {
                  const _value = e.target.value;
                  handleSearchChange(_value);
                }}
              />
              {showMultipleTags && Array.isArray(comboboxValue) && comboboxValue.length > 0 && (
                <div className="absolute inset-[1px] right-8 rounded-sm bg-base-100 z-10 px-3 flex items-center">
                  <div className="inline-block w-full truncate space-x-1 text-left">
                    {comboboxValue.map((op) => (
                      <span
                        key={op.value}
                        className="badge badge-sm bg-base-100 border-gray-200 dark:border-neutral-700 overflow-hidden"
                      >
                        {op.label}
                      </span>
                    ))}
                  </div>
                </div>
              )}
            </ComboboxButton>
            <ComboboxOptions
              anchor="bottom"
              transition
              className={clsx(
                'border shadow-lg rounded-box z-[999]',
                'bg-base-100 border-gray-200 dark:border-neutral-700',
                'w-(--input-width) [--anchor-gap:--spacing(2)]',
                'transition duration-200 ease-in-out data-closed:-translate-y-2 data-closed:opacity-0',
                popoverClassName
              )}
            >
              {querying ? (
                <ul className="flex w-full flex-col gap-2 p-2">
                  {new Array(3).fill(0).map((_, index) => (
                    <li key={index} className="skeleton w-full h-8" />
                  ))}
                </ul>
              ) : (
                <>
                  {Array.isArray(finalOptions) && finalOptions.length > 0 ? (
                    <ul className="menu w-full [--menu-active-bg:var(--color-gray-50)] dark:[--menu-active-bg:var(--color-neutral-900)]">
                      {finalOptions.map((option) => (
                        <ComboboxOption
                          as="li"
                          disabled={option.disabled}
                          key={option.value}
                          value={option}
                          className={clsx(option.disabled && 'menu-disabled')}
                        >
                          <div className="relative pr-10 break-all">
                            {option.icon}
                            {option.label}
                            {multiple && Array.isArray(finalValue) && (
                              <input
                                className="absolute right-3 checkbox checkbox-sm checkbox-primary border-0"
                                type="checkbox"
                                checked={finalValue.includes(option.value)}
                                readOnly
                              />
                            )}
                          </div>
                        </ComboboxOption>
                      ))}
                    </ul>
                  ) : (
                    <div className="grid place-items-center w-full h-20">
                      <span className="text-sm text-gray-400 dark:text-neutral-500">
                        抱歉，暂无数据
                      </span>
                    </div>
                  )}
                </>
              )}
            </ComboboxOptions>
          </div>
        );
      }}
    </Combobox>
  );
}
export const Select = React.memo(SelectNoMemoized);
